#version 450
#extension GL_ARB_separate_shader_objects : enable

const int TILE_SIZE = 16;

struct PointLight {
	vec3 pos;
	float radius;
	vec3 intensity;
    float padding;
};

#define MAX_POINT_LIGHT_PER_TILE 1023
struct LightVisiblity
{
	uint count;
	uint lightIndices[MAX_POINT_LIGHT_PER_TILE];
};

layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec2 inUV;

layout(location = 0) out vec4 outColor;

layout(binding = 0) uniform CameraUbo {
    mat4 proj;
    mat4 view;
    vec3 cameraPos;
} cameraUbo;

layout(std140, binding = 2) uniform MaterialUbo
{
    int hasAbedoMap;
    int hasNormalMap;
} materialUbo;

layout(std430, binding = 3) buffer readonly TileLightVisiblities
{
    LightVisiblity visiblities[];
} tileLightVisiblities;

layout(std140, binding = 4) uniform PointLights
{
	PointLight lights[1000];
} pointLights;

layout(binding = 5) uniform sampler2D depthImage;

layout(binding = 6) uniform sampler2D albedoImage;

layout(binding = 7) uniform sampler2D normalImage;


layout(push_constant) uniform PushConstantObject
{
	ivec2 viewportSize;
	ivec2 tileNum;
    int debugIndex;
} pushConstants;



layout(early_fragment_tests) in; // for early depth test

vec3 ApplyNormalMap(vec3 geomnor, vec3 normap)
{
    normap = normap * 2.0 - 1.0;
    vec3 up = normalize(vec3(0.001, 1, 0.001));
    vec3 surftan = normalize(cross(geomnor, up));
    vec3 surfbinor = cross(geomnor, surftan);
    return normalize(normap.y * surftan + normap.x * surfbinor + normap.z * geomnor);
}

void main()
{

    vec3 diffuse;
    if (materialUbo.hasAbedoMap > 0)
    {
        diffuse = texture(albedoImage, inUV).rgb;
    }
    else
    {
        diffuse = vec3(1.0);
    }

    vec3 normal;
    if (materialUbo.hasNormalMap > 0)
    {
        normal = ApplyNormalMap(inNormal, texture(normalImage, inUV).rgb);
    }
    else
    {
        normal = inNormal;
    }
    ivec2 tileId = ivec2(gl_FragCoord.xy / TILE_SIZE);
    uint tileIndex = tileId.y * pushConstants.tileNum.x + tileId.x;

    // debug view
    if (pushConstants.debugIndex > 1)
    {
        if (pushConstants.debugIndex == 2)
        {
			//heat map debug view
			float intensity = float(tileLightVisiblities.visiblities[tileIndex].count) / 64;
            outColor = vec4(vec3(intensity), 1.0) ; //light culling debug
			// outColor = vec4(vec3(intensity * 0.62, intensity * 0.13, intensity * 0.94), 1.0) ; //light culling debug
//			float minimum = 0.0;
//			float maximum = 1.0;
//			float ratio = 2 * (intensity - minimum) / (maximum - minimum);
//			float b = max(0, 1 - ratio);
//			float r = max(0, ratio - 1);
//			float g = max(0, 1.0 - b - r);
//		    outColor = vec4(vec3(r,g,b), 1.0);
		}
		else if (pushConstants.debugIndex == 3)
        {
            // depth debug view
            float preDepth = texture(depthImage, (gl_FragCoord.xy / pushConstants.viewportSize)).x;
            outColor = vec4(vec3(preDepth),1.0);
        }
        else if (pushConstants.debugIndex == 4)
        {
            // normal debug view
            outColor = vec4(abs(normal), 1.0);
        }
        return;
    }


    vec3 illuminance = vec3(0.0);
    uint tileLightNum = tileLightVisiblities.visiblities[tileIndex].count;
    for (int i = 0; i < tileLightNum; i++)
	{
        PointLight light = pointLights.lights[tileLightVisiblities.visiblities[tileIndex].lightIndices[i]];
		vec3 lightDir = normalize(light.pos - inPosition);
        float lambertian = max(dot(lightDir, normal), 0.0);

        if(lambertian > 0.0)
        {
            float lightDistance = distance(light.pos, inPosition);
            if (lightDistance > light.radius)
            {
                continue;
            }

            vec3 viewDir = normalize(cameraUbo.cameraPos - inPosition);
            vec3 halfDir = normalize(lightDir + viewDir);
            float specAngle = max(dot(halfDir, normal), 0.0);
            float specular = pow(specAngle, 32.0);  // TODO?: spec color & power in g-buffer?

            float att = clamp(1.0 - lightDistance * lightDistance / (light.radius * light.radius), 0.0, 1.0);
            illuminance += light.intensity * att * (lambertian * diffuse + specular);
        }
	}

    //heat map with render debug view
    if (pushConstants.debugIndex == 1)
    {
        float intensity = float(tileLightVisiblities.visiblities[tileIndex].count) / (64 / 2.0);
        outColor = vec4(vec3(intensity, intensity * 0.5, intensity * 0.5) + illuminance * 0.25, 1.0) ; //light culling debug
        return;
    }

    // render view
    outColor = vec4(illuminance, 1.0);

    //outColor = vec4(0.0, 0.0, 0.0, 1.0);
    //outColor[tileLightVisiblities.visiblities[tileIndex].count] = 1.0;
    //outColor = vec4(illuminance, 1.0);
    //outColor = vec4(abs(normal), 1.0);
    //outColor = vec4(abs(texture(normalImage, inUV).rgb), 1.0); // normal map debug view
}
